/*
 * Sylvain BERTRAND <digital.ragnarok@gmail.com>
 * code protected by GNU GPL v3
 */
#include <X11/Xlib.h>
#include <glib.h>
#include "../toolkit/display.h"
#include "../toolkit/keyboard.h"
#include "misc.h"
#include "wm.h"
#include "window.h"
#include "geom.h"
#include "stacking.h"
#include "client.h"
#include "screen.h"
#include "event.h"
#include "grab.h"
#include "debug.h"
#define GRAB_PTR_MASK (ButtonPressMask | ButtonReleaseMask | PointerMotionMask)
#define GRAB_KEY_MASK (KeyPressMask | KeyReleaseMask)
#define MASK_LIST_SIZE 8
static guint mask_list[MASK_LIST_SIZE];
static guint kgrabs = 0;
static guint pgrabs = 0;
static Time grab_time = CurrentTime;
static gint passive_count = 0;
static struct t_ic *ic = NULL;
static Time ungrab_time(void)
{
	Time t = event_time();
	if(grab_time == CurrentTime || !(t == CurrentTime || event_time_after(t, grab_time)))
		t = CurrentTime;
	return t;
}

static Window grab_window(void)
{
	return screen_support_win;
}

gboolean grab_on_keyboard(void)
{
	return kgrabs > 0;
}

gboolean grab_on_pointer(void)
{
	return pgrabs > 0;
}

struct t_ic *grab_input_context(void)
{
	return ic;
}

gboolean grab_keyboard_full(gboolean grab)
{
	gboolean ret = FALSE;
	if(grab) {
		if(kgrabs++ == 0) {
			ret = XGrabKeyboard(t_display, grab_window(), False, GrabModeAsync, GrabModeAsync, event_time()) == Success;
			if(!ret)
				--kgrabs;
			else {
				passive_count = 0;
				grab_time = event_time();
			}
		} else
			ret = TRUE;
	} else if(kgrabs > 0) {
		if(--kgrabs == 0) {
			XUngrabKeyboard(t_display, ungrab_time());
		}
		ret = TRUE;
	}
	return ret;
}

gboolean grab_pointer_full(gboolean grab, gboolean owner_events, gboolean confine, enum wm_cursor cur)
{
	gboolean ret = FALSE;
	if(grab) {
		if(pgrabs++ == 0) {
			ret =
				XGrabPointer(t_display, grab_window(), owner_events, GRAB_PTR_MASK, GrabModeAsync,
				GrabModeAsync, (confine ? DefaultRootWindow(t_display) : None), wm_cursor(cur), event_time()) == Success;
			if(!ret)
				--pgrabs;
			else
				grab_time = event_time();
		} else
			ret = TRUE;
	} else if(pgrabs > 0) {
		if(--pgrabs == 0) {
			XUngrabPointer(t_display, ungrab_time());
		}
		ret = TRUE;
	}
	return ret;
}

gint grab_server(gboolean grab)
{
	static guint sgrabs = 0;
	if(grab) {
		if(sgrabs++ == 0) {
			XGrabServer(t_display);
			XSync(t_display, FALSE);
		}
	} else if(sgrabs > 0) {
		if(--sgrabs == 0) {
			XUngrabServer(t_display);
			XFlush(t_display);
		}
	}
	return sgrabs;
}

void grab_startup(void)
{
	guint i = 0;
	guint num, caps, scroll;
	num = t_keyboard_modkey_to_modmask(T_KEYBOARD_MODKEY_NUMLOCK);
	caps = t_keyboard_modkey_to_modmask(T_KEYBOARD_MODKEY_CAPSLOCK);
	scroll = t_keyboard_modkey_to_modmask(T_KEYBOARD_MODKEY_SCROLLLOCK);
	mask_list[i++] = 0;
	mask_list[i++] = num;
	mask_list[i++] = caps;
	mask_list[i++] = scroll;
	mask_list[i++] = num | caps;
	mask_list[i++] = num | scroll;
	mask_list[i++] = caps | scroll;
	mask_list[i++] = num | caps | scroll;
	g_assert(i == MASK_LIST_SIZE);
	ic = t_keyboard_context_new(DefaultRootWindow(t_display), grab_window());
}

void grab_shutdown(void)
{
	t_keyboard_context_unref(ic);
	ic = NULL;
	while(ungrab_keyboard());
	while(ungrab_pointer());
	while(grab_server(FALSE));
}

void grab_button_full(guint button, guint state, Window win, guint mask, gint pointer_mode, enum wm_cursor cur)
{
	guint i;
	t_display_ignore_errors(TRUE);
	for(i = 0; i < MASK_LIST_SIZE; ++i)
		XGrabButton(t_display, button, state | mask_list[i], win, False, mask, pointer_mode, GrabModeAsync, None,
			wm_cursor(cur));
	t_display_ignore_errors(FALSE);
	if(t_display_error_occured)
		wm_debug("Failed to grab button %d modifiers %d", button, state);
}

void ungrab_button(guint button, guint state, Window win)
{
	guint i;
	for(i = 0; i < MASK_LIST_SIZE; ++i)
		XUngrabButton(t_display, button, state | mask_list[i], win);
}

void grab_key(guint keycode, guint state, Window win, gint keyboard_mode)
{
	guint i;
	t_display_ignore_errors(TRUE);
	for(i = 0; i < MASK_LIST_SIZE; ++i)
		XGrabKey(t_display, keycode, state | mask_list[i], win, FALSE, GrabModeAsync, keyboard_mode);
	t_display_ignore_errors(FALSE);
	if(t_display_error_occured)
		wm_debug("Failed to grab keycode %d modifiers %d", keycode, state);
}

void ungrab_all_keys(Window win)
{
	XUngrabKey(t_display, AnyKey, AnyModifier, win);
}

void grab_key_passive_count(int change)
{
	if(grab_on_keyboard())
		return;
	passive_count += change;
	if(passive_count < 0)
		passive_count = 0;
}
